<template><div><h2 id="背景介绍" tabindex="-1"><a class="header-anchor" href="#背景介绍"><span>背景介绍</span></a></h2>
<p>今天测试提了个Bug，说我们的商品库存和订单记录对不上，然后仔细看了下数据库数据，的确有订单记录，但是库存为啥对不上呢？</p>
<p>随后，对问题进行了分析，也去看了一下MySQL的日志，发现是由于MySQL死锁导致的数据不一致问题，MySQL选择了回滚更新库存的SQL语句，这才导致只有订单记录，库存信息却没有改变的原因。</p>
<h2 id="分析问题" tabindex="-1"><a class="header-anchor" href="#分析问题"><span>分析问题</span></a></h2>
<p>刚刚也提到过，先分析，后看的MySQL日志。
怎么看的呢？</p>
<div class="language-SQL line-numbers-mode" data-ext="SQL" data-title="SQL"><pre v-pre class="language-SQL"><code>SHOW ENGINE INNODB STATUS;
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p>看到输出的日志中，出现了&quot;LATEST DETECTED DEADLOCK&quot;部分才确定是死锁的原因。</p>
<p>随后看了下后面的详细的报错信息，才定位到具体是那些SQL导致的。通过SQL语句反推业务代码，这才发现，两个并发事务：</p>
<ul>
<li>事务1：先执行扣减操作，在更新订单记录。</li>
<li>事务2：先更新了订单记录，再去执行扣减库存的操作。</li>
</ul>
<p>由于访问顺序的问题，导致双方分别持有对方的锁，才导致的死锁问题。</p>
<h2 id="解决方案" tabindex="-1"><a class="header-anchor" href="#解决方案"><span>解决方案</span></a></h2>
<p>最后，对业务逻辑代码进行了调整，保持一致的访问顺序，并使用乐观锁（版本号）的方式，加以保证。</p>
<p>目前对于死锁的采取措施如下：</p>
<ol>
<li><strong>保持一致的访问顺序</strong>
<ul>
<li>修改应用程序的代码，确保所有事务按照相同的顺序访问表。</li>
</ul>
</li>
<li><strong>减少事务范围</strong>
<ul>
<li>尽可能缩小事务作用范围，减少持有锁的时间。</li>
</ul>
</li>
<li><strong>使用乐观锁</strong>
通过在表中加入字段version，在更新时进行判别。</li>
<li><strong>添加适当的索引</strong></li>
<li><strong>使用行级锁而不是表级锁</strong>
众所周知，InnoDB默认使用的是行级锁，但可以由于某些特殊情况，会升级称为表级锁，而表级锁会导致一定的阻塞，严重的会产生死锁问题，所以在编码过程中也要考虑这个因素。</li>
</ol>
<p><strong>如何监控和预防：</strong></p>
<ol>
<li><strong>设置死锁监控</strong>
以下SQL可以把所有的死锁信息记录到MySQL的错误日志中。</li>
</ol>
<div class="language-SQL line-numbers-mode" data-ext="SQL" data-title="SQL"><pre v-pre class="language-SQL"><code>    SET GLOBAL innodb_print_all_deadlocks = 1;
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><ol start="2">
<li><strong>定期检查死锁情况</strong>
以下SQL可以查看当前正在执行的事务。</li>
</ol>
<div class="language-SQL line-numbers-mode" data-ext="SQL" data-title="SQL"><pre v-pre class="language-SQL"><code>    SELECT * FROM information_scheme.INNODB_TRX;
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><ol start="3">
<li><strong>使用性能模式（Performance Scheme）来监控锁等待</strong></li>
</ol>
<div class="language-SQL line-numbers-mode" data-ext="SQL" data-title="SQL"><pre v-pre class="language-SQL"><code>    SELECT * FROM performance_scheme.events_waits_cuurent
    WHERE EVENT_NAME LIKE 'wait/synch/mutex/innodb%';
</code></pre><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div></div></div><p>好了，今天这个Bug的分析与修复就此结束了，希望自己引以为戒，勿再犯同样的问题。</p>
</div></template>


